<template>
  <div
    class="widgets-layout"
    :class="{ 'four-scroll-y': showScroll && layout.length > 0 }"
  >
    <div v-if="layout.length === 0" class="noWidgetsTips">
      请拖拽图表或其他控件至此处
    </div>
    <!-- 画布组件 -->
    <grid-layout
      v-else
      ref="gridLayout"
      :layout="layout"
      :col-num="dataStore.gridColNum"
      :row-height="dataStore.gridRowHeight"
      :is-draggable="!isNotAllowed"
      :is-resizable="!isNotAllowed"
      :is-mirrored="false"
      :vertical-compact="true"
      :prevent-collision="false"
      :margin="dataStore.girdMargin"
      :use-css-transforms="true"
      @height-updated="heightUpdated"
      @layout-updated="layoutUpdated"
    >
      <template v-for="(item, index) in layout">
        <div :key="`${item.i}-${item.widgetId}`" :class="`box-item-${item.i}`">
          <grid-item
            :id="`grid-item-${item.i}`"
            ref="gridItems"
            :key="item.i"
            :visitor-mode="isVisitor || item.static || isLock"
            :index="index"
            :static="item.static"
            :class="getGridItemClass(item)"
            :style="getGridStyle(item)"
            :scale="dataStore.scaleValue"
            drag-allow-from=".grid-item-drag-handler"
            :is-draggable="item.draggable"
            :x="item.x"
            :y="item.y"
            :w="item.w"
            :h="item.h"
            :i="item.i"
            :min-w="6"
            :min-h="3"
            @move="move"
            @cornermouseup="cornermouseup"
            @resized="resized"
            @dragstart="dragstart"
            @dragmove="dragmove"
            @dragend="dragend"
            @resizemove="resizemove"
          >
            <div v-if="!isNotAllowed" class="grid-item-drag-handler"></div>
            <div v-if="item.isLoading" class="loading-mask">
              <div class="loading-status">
                <a-icon class="sync-icon" type="sync" spin /><span
                  class="loading-text"
                  >加载中...</span
                >
              </div>
            </div>
            <div class="drag-marker" :class="{ 'is-loading': item.isLoading }">
              <div
                v-if="dataStore.SettingMode === 'interaction'"
                class="id-box"
              >
                ID:{{ item.i }}
              </div>
              <a-tooltip
                v-if="
                  item.chartType === 'configuration' ||
                  isReverseSimulation(item)
                "
                placement="top"
                :title="
                  configurationExecuteId !== item.widgetId ? '执行' : '停止'
                "
              >
                <div
                  v-if="!isVisitor && !isPreview && !isLock"
                  style="display: inline-block;"
                >
                  <a-icon-font
                    v-if="configurationExecuteId !== item.widgetId"
                    type="iconyunhang"
                    :class="{
                      'grid-item-execute': true,
                      disable: configurationExecuteId,
                    }"
                    @click.stop="saveConfigurationParams(item)"
                  />
                  <a-icon-font
                    v-else
                    type="icontingzhi"
                    class="grid-item-execute"
                    @click.stop="stopConfigurationExecute()"
                  />
                </div>
              </a-tooltip>
              <filter-form-toolbar
                v-if="
                  isFilterForm(item) &&
                  isFilterToolbarEnabled(item) &&
                  !isVisitor &&
                  !isLock
                "
                :grid-item="item"
                :index="index"
                :disabled="isFilterFormRunning"
                :is-running="checkIsRunning(item)"
                @start="startfilter(item)"
                @stop="stopfilter(item)"
                @reset="resetfilter(item)"
              />
              <a-tooltip
                v-if="
                  item.chartType !== 'upSplit' && item.chartType !== 'downSplit'
                "
                placement="top"
                title="点击复制"
              >
                <div class="grid-item-copy" @click.stop="copyChart(item)">
                  <a-icon-font v-if="!isNotAllowed" type="iconfuzhi" />
                </div>
              </a-tooltip>
              <a-tooltip placement="top" title="点击删除">
                <div class="grid-item-close" @click.stop="deleteChart(item)">
                  <a-icon-font v-if="!isNotAllowed" type="iconicon-beifen5" />
                </div>
              </a-tooltip>
            </div>
            <div
              v-if="
                item.widgetId === configurationExecuteId || checkIsRunning(item)
              "
              class="progress-bar"
            ></div>
            <!-- 联动遮盖 -->
            <div
              v-if="hideWidgetList.includes(item.widgetId)"
              :class="{
                'interact-mask': true,
                'no-title':
                  !item.chartOptions.title || !item.chartOptions.showChart,
              }"
            ></div>
            <div
              v-if="
                !item.showChart &&
                item.chartOptions &&
                item.chartType !== 'filter'
              "
              class="title-box"
              :class="{
                'no-title':
                  [undefined, null, ''].includes(item.chartOptions.title) ||
                  (item.chartType === 'text' && !item.chartOptions.showTitle),
              }"
              @click.stop="gridItemClick(item)"
            >
              {{ item.chartOptions ? item.chartOptions.title || '' : '' }}
            </div>
            <!-- 无数据 -->
            <div
              v-if="showNoData(item)"
              class="no-data"
              @click.stop="gridItemClick(item)"
            >
              <div class="no-data-block">
                <img
                  v-if="item.showChart"
                  class="no-data-img"
                  alt="no-data"
                  draggable="false"
                  :src="getNoDataImgSrc()"
                />
                <img
                  v-else
                  class="no-data-img"
                  alt="empty"
                  draggable="false"
                  :src="
                    `${publicPath}${getChartImage(item.chartType)}` ||
                    '@/assets/view/empty.png'
                  "
                />
                <span class="no-data-text">{{
                  errorFilterWidgets.includes(item.i)
                    ? errorMsgMap[item.i]
                    : '暂无数据'
                }}</span>
              </div>
            </div>
            <div
              :style="getBoxHeightStyle(item)"
              @click.stop="gridItemClick(item)"
            >
              <pageTableVue
                v-if="item.chartType === tableTypeName"
                :id="`grid-${item.i}`"
                :ref="`chart-${item.i}`"
                :is-sortable="true"
                :page-disabled="false"
                :config="item"
                @pagechange="
                  (pageConfig) => onTablePageChange(item, pageConfig)
                "
                @sortchange="
                  (sortData) => {
                    onTableSortchange(item, index, sortData)
                  }
                "
                @columnWidthChange="
                  (widthData) => onTableColumnWidthChange(item, widthData)
                "
              >
              </pageTableVue>
              <vis-tabs
                v-else-if="item.chartOptions.chartType === vistabsTypeName"
                :ref="item.i"
                :item-data="item"
              >
              </vis-tabs>
              <text-editor
                v-else-if="item.chartOptions.chartType === 'text'"
                :id="`box-item-${item.i}`"
                class="text-editor"
                :class="{
                  'pre-pub-mode': isPreview || isPublish,
                }"
                :read-only="isNotAllowed"
                :text-value="item.chartOptions.value"
                @handle-click="gridItemClick(item)"
                @render-complete="removeLoading(item)"
                @highlight-formula="highlightFormula"
                @editor-blur="handleTextBlur(item)"
                @add-error-border="addErrorBorder(item.i)"
                @remove-error-border="removeErrorBorder(item.i)"
                @text-change="
                  (val) => {
                    handleTextChange(item, val)
                  }
                "
              ></text-editor>
              <div
                v-else-if="isFilterForm(item)"
                class="filter-form-hander"
                @mouseenter="handleFilterFormMouseEnter(item)"
                @mouseleave="handleFilterFormMouseLeave()"
              >
                <filter-form
                  v-if="showFilterForm()"
                  :ref="`filter-form-${item.i}`"
                  :config="item"
                  :dataset="dataset"
                  :pipeline-id="pipelineId"
                  :node-id-map="publishMappingJson"
                  :disabled="isFilterFormRunning || isVisitor || isLock"
                  @change="
                    (dataSet) => {
                      filterChange(dataSet, item)
                    }
                  "
                >
                </filter-form>
              </div>
              <operator-configuration
                v-else-if="item.chartType === 'configuration'"
                :config="item.chartOptions"
                :disabled="
                  configurationExecuteId === item.widgetId ||
                  isVisitor ||
                  isLock
                "
              />
              <div v-else-if="item.chartType === 'upSplit'" class="up-split">
                <div class="blank"></div>
                <div class="up-split-box">
                  <div v-if="!(isPreview || isPublish)" class="top-left"></div>
                  <div v-if="!(isPreview || isPublish)" class="top-right"></div>
                </div>
              </div>
              <div
                v-else-if="item.chartType === 'downSplit'"
                class="down-split"
              >
                <div class="down-split-box">
                  <div
                    v-if="!(isPreview || isPublish)"
                    class="bottom-left"
                  ></div>
                  <div
                    v-if="!(isPreview || isPublish)"
                    class="bottom-right"
                  ></div>
                </div>
                <div class="blank"></div>
                <div class="gap"></div>
              </div>
              <template v-else>
                <Chart
                  v-if="item.widgetId || gridItemIsGeoMap(item)"
                  :id="`grid-${item.i}`"
                  :ref="`chart-${item.i}`"
                  class="chart-design"
                  :config="item.chartOptions"
                  :chart-type="item.chartType"
                  @chartinit="
                    (instance) => {
                      onChartInstanceInit(instance, item)
                    }
                  "
                  @chartevent="
                    (data) => {
                      onChartEvent(data, item)
                    }
                  "
                >
                </Chart>
              </template>
            </div>
          </grid-item>
        </div>
      </template>
    </grid-layout>
  </div>
</template>

<script lang="ts">
import { Vue, Component, Prop } from 'vue-property-decorator'
import VisualizationStore from '@/store/modules/visualization'
import VisualizationPreviewStore from '@/store/modules/visualization-preview'
import UserStore from '@/store/modules/user'
import GridLayout from '@/components/vueGridLayout/components/GridLayout.vue'
import GridItem from '@/components/vueGridLayout/components/GridItem.vue'
import Chart from '@/components/chart/chart.vue'
import VisTabs from '@/components/vistabs/vistabs.vue'
import TextEditor from '../textEditor/textEditor.vue'
import pageTableVue from '../pageTable/pageTable.vue'
import OperatorConfiguration from '@/components/visualization/OperatorConfiguration.vue'
import FilterForm from '@/components/filter-form/index.vue'
import FilterFormToolbar from '@/components/filter-form/filter-form-toolbar.vue'

import {
  GridItemDataInterface,
  KeyValueStore,
  ControlConfigurationInterface,
  componentTypeEnum,
  gridItemFilterInterface,
} from '../vueGridLayout/interfate/grid-Item-data-inteface'

import { VisTabType } from '../vistabs/vistabs-interface'

import { isArray, cloneDeep, isString } from 'lodash'

import { publicPath } from '@/api/constants'
import {
  buildSaveParameters,
  mergeFilterFormFilters,
  checkIsFilterForm,
  filterGraphData,
  getFilterDataFromFilterForm,
  checkFilterFormIsEnabled,
} from '@/util/common'
import {
  updateWidget,
  getWidgetData,
  saveWidget,
  simpleUpdate,
  pipelineStop,
  pipelineExecute,
  deleteWidget,
  deleteWidgetBatch,
} from '@/api/widget'
import {
  filterFormTypeName,
  GRAPH_ARRAY,
  GEOMAP_NAME,
  vistabsTypeName,
  CONFIGURATION_NAME,
  getChartIconImage,
  tableTypeName,
  GIS_VIEW,
} from '@/config/contant'
import {
  parseGetDataResult,
  getWidgetGraphData,
} from '../common/VisualizationDashboard/util'
import { enableConvertToCoor } from '@/util/recommend-visualization'
import { HoverFilterFormInfo } from './util'
import DataViewStore from '@/store/modules/dataview'

enum TargetContainerType {
  PANEL = 'panel',
  TABS = 'tabs',
}

@Component({
  components: {
    GridLayout,
    GridItem,
    TextEditor,
    pageTableVue,
    FilterForm,
    FilterFormToolbar,
    Chart,
    VisTabs,
    OperatorConfiguration,
  },
})
export default class WidgetsLayout extends Vue {
  @Prop({ type: Array, default: [] }) layout!: Array<GridItemDataInterface>

  @Prop({ type: Boolean, default: false }) showScroll?: Boolean

  @Prop({ type: String, default: TargetContainerType.PANEL })
  targetContainer?: String

  private publicPath = publicPath

  /** 用于存储code为1106的response的widget */
  public errorFilterWidgets = [] as Array<any>
  /** 错误消息 */
  public errorMsgMap = {} as any

  /** 鼠标active状态下的filter-form信息 */
  hoverFilterFormInfo: HoverFilterFormInfo | null = null

  // 反向模拟推演 demo start *********
  private get allWidgetList() {
    return this.dataStore.widgetList
  }
  private reverseSimulationValue = 0 //
  // 反向模拟推演 demo end *********

  vistabsTypeName: string = vistabsTypeName
  tableTypeName: string = tableTypeName
  CONFIGURATION_NAME = CONFIGURATION_NAME

  // 记录当前起效果的筛选器filter条件，table组件比较特殊可以filter组件和内部 排序/分页 两种一起筛选
  private executeFilterFormData: any = {}

  // ========> For vuex store data
  get dataStore() {
    if (this.isPreview) {
      return VisualizationPreviewStore
    }
    return VisualizationStore
  }

  get gridList() {
    return this.dataStore.gridList
  }

  get publishNo() {
    return VisualizationStore.publishNo.length > 0
      ? VisualizationStore.publishNo
      : undefined
  }

  get isNotAllowed() {
    return this.isVisitor || this.isPreview || this.isPublish || this.isLock
  }

  get isVisitor() {
    //  访客身份
    return UserStore.isProjectGuest
  }

  get isPreview() {
    // 预览状态
    return VisualizationStore.visualizationMode === 'preview'
  }

  get isPublish() {
    // 发布状态
    return VisualizationStore.visualizationMode === 'publish'
  }

  get isLock() {
    return DataViewStore.getLock
  }

  get isEdit() {
    return VisualizationStore.visualizationMode === 'edit'
  }

  private get currentDashboard() {
    return this.dataStore.currentDashboard
  }

  get dashBoardId() {
    return this.dataStore.currentDashboard.id
  }

  get themeSetting() {
    return this.dataStore.layoutData.themeSetting
  }

  get publishMappingJson() {
    if (this.isPublish) {
      // @ts-ignore
      return this.dataStore.publishMappingJson
    }
    return {}
  }

  // 控件执行后的sessionId
  public configurationExecuteSessionId: string = ''
  get configurationExecuteId() {
    return this.dataStore.configurationExecuteId
  }

  get filterFormExecuteId() {
    return this.dataStore.filterFormExecuteId
  }

  /** 当前激活状态下的filter-chart的关联图表 */
  get currentFilterFormAsCharts(): Array<string | number> {
    return this.dataStore.currentFilterFormAsCharts || []
  }

  get pipelineId() {
    return this.dataStore.pipelineId
  }

  get dataset(): any[] {
    return this.dataStore.dataset
  }

  get projectId() {
    return (this.$route.params as any).id || localStorage.getItem('projectId')
  }

  get loadingMaps() {
    return this.dataStore.loadingMaps
  }

  /** 需要隐藏的widget 列表 */
  get hideWidgetList() {
    return this.dataStore.hideWidgetList
  }

  /** 检测filterForm是否正在执行 */
  get isFilterFormRunning() {
    return this.dataStore.isSettingPanelChangeLock
  }

  /** filter是否可用 */
  isFilterToolbarEnabled(gridItem: GridItemDataInterface) {
    return checkFilterFormIsEnabled(gridItem)
  }

  /** 获取hover状态下的filter-form的关联图表 */
  get filterAssociationChartHover() {
    let result = [] as Array<string | number>
    if (this.hoverFilterFormInfo) {
      const currentGrid = this.gridList.find(
        (item) => item.i === this.hoverFilterFormInfo?.i
      )
      result = currentGrid?.chartOptions?.associationChart || []
    }
    return result
  }

  get currentHoverMyComponent() {
    return VisualizationStore.getHoverChart
  }

  // ========>  Common function

  /** 判断是否要显示无数据状态 */
  private showNoData(item: GridItemDataInterface) {
    const chartType = Array.isArray(item.chartType)
      ? item.chartType[1]
      : item.chartType
    const ignoreChartTypeList = [
      'text',
      vistabsTypeName,
      'upSplit',
      'downSplit',
      CONFIGURATION_NAME,
      GIS_VIEW,
    ]
    if (ignoreChartTypeList.includes(chartType)) {
      return false
    }

    if (item.isLoading) {
      return false
    }

    if (!item.chartOptions) {
      return false
    }

    if (!this.checkIsNoData(item)) {
      return false
    }

    return true
  }

  private getNoDataImgSrc() {
    return ['dark', 'nebula'].includes(this.themeSetting.theme)
      ? '/img/no-data-dark.png'
      : '/img/no-data.png'
  }
  /** 获取box 高度样式 */
  private getBoxHeightStyle(item: GridItemDataInterface) {
    if (item.chartType === 'text' && !item.chartOptions.showTitle) {
      return {
        height: '100%',
      }
    }
    if (
      !item.showChart &&
      item.chartOptions &&
      item.chartType !== 'filter' &&
      item.chartOptions.title
    ) {
      return {
        height: 'calc(100% - 38px)',
      }
    }
    return {
      height: '100%',
    }
  }

  getGridDom(gridItem: GridItemDataInterface) {
    const documentWin = document as any
    return documentWin.querySelector(`#grid-item-${gridItem.i}`)
  }

  gridItemClick(item: GridItemDataInterface) {
    this.filterFormClicked(item)
    const index = this.gridList.findIndex((gridItem) => gridItem.i === item.i)

    const isGraph =
      item.chartType &&
      isArray(item.chartType) &&
      item.chartType[1] === 'graph_net'
    if (isGraph) {
      this.dataStore.setCurrentGridItem(null)
      this.dataStore.setActiveSettingItem('background')
      // this.dataStore.setSettingMode('property') // 将设置模式归位
      this.$message.error('graph 组件暂不支持配置')
      return
    }

    if (item.chartType === 'upSplit' || item.chartType === 'downSplit') {
      return
    }

    this.dataStore.setCurrentGridItem(item)
    if (item.chartType === 'text') {
      this.dataStore.setActiveSettingItem('text')
      //  set item non-draggable when edit mode; click item fetch formulaList;
      if (index !== -1) {
        this.$set(item, 'draggable', false)
        //  set toolbar position align with text comp
        this.handleToolbarAtBorder(item, 'focus')
      }

      return
    }

    this.dataStore.setActiveSettingItem('chart')
  }

  getGridItemClass(item: GridItemDataInterface) {
    const classList = ['grid-item', `grid-item-${item.i}`]

    if (this.currentDashboard.type === '报告') {
      classList.push('report-mode')
    }

    if (
      item.static ||
      (item.chartType === 'text' &&
        !item.chartOptions.showBorder &&
        (this.isPublish || this.isPreview))
    ) {
      classList.push('disable-border')
    }

    if (
      this.filterAssociationChartHover.includes(item.widgetId) ||
      this.currentFilterFormAsCharts.includes(item.widgetId)
    ) {
      classList.push('active')
    }

    // 加载中、正在拖动的情况下不需要此种样式
    if (
      !item.isLoading &&
      Number.parseInt(item.uniqueIDFromMyComponent, 10) ===
        this.currentHoverMyComponent
    ) {
      classList.push('grid-item-highlight')
    }

    return classList.join(' ')
  }

  getGridStyle(item: GridItemDataInterface) {
    const { currentGridItem, activeSettingItem } = this.dataStore
    if (
      item.chartOptions.pipelineId &&
      item.chartOptions.pipelineId ===
        currentGridItem?.chartOptions.pipelineId &&
      activeSettingItem === 'chart'
    ) {
      return `border: 1px solid ${
        currentGridItem?.i === item.i
          ? 'var(--component-border-color-active)'
          : 'var(--component-border-color-hover)'
      }; box-shadow: ${
        currentGridItem?.i === item.i
          ? 'var(--component-shadow-active)'
          : 'var(--component-shadow)'
      };`
    }
    // 为确保和组件联动样式一致，这里将当前选中组件边框颜色改为 rgb(105, 115, 255) 原先为 rgba(87, 96, 230,0.45)
    return this.dataStore.currentGridItem &&
      this.dataStore.currentGridItem.i === item.i &&
      this.dataStore.activeSettingItem !== 'background'
      ? 'border: 1px solid var(--component-border-color-active); box-shadow: var(--component-shadow-active);'
      : 'box-shadow: var(--component-shadow);'
  }

  getChartImage(chartType: Array<string> | string) {
    return getChartIconImage(chartType, this.themeSetting.theme)
  }

  checkIsNoData(gridItem: GridItemDataInterface) {
    /** 检查是否有数据 */
    if (this.gridItemIsGeoMap(gridItem)) {
      return false
    }
    if (this.gritItemIsFilterForm(gridItem)) {
      return false
    }
    if (this.gridItemIsVistabs(gridItem)) {
      return false
    }
    const chartType = Array.isArray(gridItem.chartType)
      ? gridItem.chartType[1]
      : gridItem.chartType
    if (GRAPH_ARRAY.includes(chartType)) {
      const { nodes } = gridItem.chartOptions.value || {}
      return !(nodes && Object.keys(nodes).length > 0)
    }
    if (isArray(gridItem.chartOptions.value)) {
      return gridItem.chartOptions.value.length === 0
    }
    return false
  }

  /** 获取gridIndex */
  getGridIndex(attributeValue: string | number, attributeName: string = 'i') {
    return this.gridList.findIndex(
      (item: any) => item[attributeName] === attributeValue
    )
  }

  /** 释放加载标识 */
  releaseLoadingTag(widgetId: string | number) {
    this.loadingMaps[`widget_${this.filterFormExecuteId}`] =
      this.loadingMaps[`widget_${this.filterFormExecuteId}`] || []
    this.$set(
      this.loadingMaps,
      `widget_${this.filterFormExecuteId}`,
      this.loadingMaps[`widget_${this.filterFormExecuteId}`].filter(
        (id: any) => id !== widgetId
      )
    )

    setTimeout(() => {
      if (this.loadingMaps[`widget_${this.filterFormExecuteId}`].length === 0) {
        this.dataStore.setIsSettingPanelChangeLock(false)
      }
    }, 1000)
  }

  // ========> For Grid Item event
  move(id: any) {
    this.dataStore.eventBus.$emit('widget-item-move', id)
  }
  moved(id: any) {
    const item: any = this.layout.find((gridItem) => gridItem.i === id)

    this.dataStore.eventBus.$emit('widget-item-moved', item)
  }
  layoutUpdated() {
    this.dataStore.eventBus.$emit('sub-layout-updated')
  }
  heightUpdated(data: any) {
    if (this.targetContainer === TargetContainerType.PANEL) {
      this.dataStore.eventBus.$emit('widget-height-updated', data)
    }
  }
  /** 画布布局 */
  flexLayout() {
    const gridLayout = this.$refs.gridLayout as any
    if (gridLayout) {
      gridLayout.flexLayout()

      this.layout.forEach((gridItem: GridItemDataInterface) => {
        // 如果当前组件是tabs，则内部的gridLayou再执行一遍flexLayout
        if (this.gridItemIsVistabs(gridItem)) {
          const vistabs = this.$refs[gridItem.i] as any
          if (vistabs) {
            vistabs[0].flexLayout()
          }
        }
      })
    }
  }
  /** 删除gridItem */
  deleteItem(item: GridItemDataInterface, index: number) {
    const chartId = item.i as string
    const dataId = item.chartOptions.dataId as string
    const needDelControls: Array<string> = []
    // 同步清空关联控件
    this.gridList.forEach((gridItem) => {
      if (gridItem.chartType === 'filter' && gridItem.dataId === dataId) {
        const fieldConfigNew = [] as Array<ControlConfigurationInterface>
        ;(gridItem.fieldConfig as Array<ControlConfigurationInterface>).forEach(
          (fieldInfo) => {
            if (fieldInfo.associationChart.includes(chartId)) {
              if (fieldInfo.associationChart.length > 1) {
                fieldInfo.associationChart = fieldInfo.associationChart.filter(
                  (chartIdField) => chartIdField !== chartId
                )
                fieldConfigNew.push(fieldInfo)
              }
            } else {
              fieldConfigNew.push(fieldInfo)
            }
          }
        )
        if (fieldConfigNew.length > 0) {
          this.$set(gridItem, 'fieldConfig', fieldConfigNew)
        } else {
          needDelControls.push(gridItem.i as string)
        }
      }
    })

    if (item.widgetId && !checkIsFilterForm(item)) {
      this.clearFilterFormAssociationChart(item.widgetId)
    }

    this.dataStore.setCurrentGridItem(null)
    this.dataStore.setActiveSettingItem('background')
    this.dataStore.removeGridItem(index, 1)
    this.dataStore.setRecommendBaseSpec(null)
    if (needDelControls.length > 0) {
      const gridListNew = this.gridList.filter(
        (gridItem) => !needDelControls.includes(gridItem.i as string)
      )
      this.dataStore.setGridList(gridListNew)
      setTimeout(() => {
        this.flexLayout()
      }, 10)
    } else {
      this.onSave()
    }
  }
  /** 获取 */
  getBindChartIds(item: gridItemFilterInterface) {
    const chartids: KeyValueStore = {}
    if (item.chartType === 'filter') {
      item.fieldConfig.forEach((fieldItem: KeyValueStore) => {
        if (
          fieldItem.associationChart &&
          fieldItem.associationChart.length > 0
        ) {
          ;(fieldItem.associationChart as Array<string>).forEach((chartId) => {
            chartids[chartId] = chartId
          })
        }
      })
    }
    return chartids
  }

  deleteVisTabs(item: GridItemDataInterface) {
    const tabItems: any = [] // 保存所有待删除组件的gridItem.i数据
    const tabIds: string[] = []
    item.chartOptions.tabs.forEach((tab: VisTabType) => {
      tabIds.push(tab.id)
    })

    this.gridList.forEach((gridItem) => {
      if (tabIds.includes(gridItem.chartOptions.parentId)) {
        tabItems.push(gridItem.i)
      }
    })

    if (tabItems.length > 0) {
      // 有子组件，二次提醒
      this.$confirm({
        content: '该组件中含有图表或控件内容，是否确认删除？',
        okText: '确定',
        cancelText: '取消',
        onOk: () => {
          const widgetIds: any = []
          tabItems.push(item.i) // 将vistabs也作为删除的元素加进来
          for (let i = this.gridList.length - 1; i >= 0; i -= 1) {
            const gridItem = this.gridList[i]
            if (
              tabItems?.includes(gridItem.i.toString()) &&
              gridItem.widgetId
            ) {
              // 获取要删除的item.widgetId
              widgetIds.push(gridItem.widgetId)
              // 删除元素
              this.dataStore.removeGridItem(i, 1)
            }
          }

          deleteWidgetBatch({
            data: {
              projectId: this.projectId,
              widgetIds,
            },
          }).then(() => {
            this.onSave()
          })
        },
      })
    } else {
      deleteWidget({
        data: {
          projectId: this.projectId,
          id: item.widgetId,
        },
      }).then(() => {
        const index = this.gridList.findIndex(
          (gridItem) => gridItem.i === item.i
        )
        this.dataStore.removeGridItem(index, 1)
        this.onSave()
      })
    }
  }

  deleteChart(item: GridItemDataInterface) {
    const index = this.gridList.findIndex((gridItem) => gridItem.i === item.i)
    if (this.isNotAllowed) {
      return
    }

    // 如果是vistabs，需要批量删除内部的所有组件，单独处理
    if (item.chartType === vistabsTypeName) {
      this.deleteVisTabs(item)
      return
    }

    if (!item.widgetId) {
      if (item.chartType === 'table') {
        this.deleteItem(item, index)
      } else {
        let chartids: KeyValueStore = {}
        if (item.chartType === 'filter') {
          chartids = this.getBindChartIds(item as gridItemFilterInterface)
        }
        this.dataStore.removeGridItem(index, 1)
        this.dataStore.setCurrentGridItem(null)
        this.dataStore.setActiveSettingItem('background')
        this.$nextTick(() => {
          this.onSave(false, () => {
            setTimeout(() => {
              this.gridList.forEach((gridItem, gridItemindex) => {
                if (gridItem.chartType !== 'filter' && chartids[gridItem.i]) {
                  if (
                    (gridItem.chartType === 'table' || gridItem.widgetId) &&
                    gridItem.formData &&
                    gridItem.formData.dataId
                  ) {
                    this.getWidgetData(
                      gridItem.formData as KeyValueStore,
                      gridItem,
                      gridItemindex
                    )
                  }
                }
              })
            }, 500)
          })
        })
      }
    } else {
      deleteWidget({
        data: {
          projectId: this.projectId,
          id: item.widgetId,
        },
      }).then(() => {
        this.deleteItem(item, index)
      })
    }
  }
  copyChart(item: GridItemDataInterface) {
    this.$set(item, 'isCopying', true)
    this.dataStore.eventBus.$emit('widget-on-copy', item, () => {
      this.$set(item, 'isCopying', false)
    })
  }
  /** 保存数据 */
  onSave(noMessage: boolean = true, callback?: Function) {
    console.log('widget-on-save')
    this.dataStore.eventBus.$emit('widget-on-save', noMessage, callback)
  }
  dragstart() {
    this.dataStore.setDraggingFlag(true)
  }
  dragend(grid: GridItemDataInterface) {
    this.dataStore.setDraggingFlag(false)
    this.dataStore.setDraggingItem(null)
    this.moved(grid.i)
  }
  dragmove(grid: any) {
    this.dataStore.eventBus.$emit('widget-drag-move', grid)
  }

  /**
   * 单个组件大小调整后，除了要更新自己的大小，还涉及循环子组件的大小更新
   */
  updateChartSize(item: GridItemDataInterface, index: number) {
    // table和vistabs组件调整大小之后，也需要更新widget，复制组件的时候，才能拿到最新的w、h
    const isNotChart =
      isString(item.chartType) && ['filter'].includes(item.chartType)

    if (isNotChart) {
      return
    }

    const parentNode = this.getGridDom(item)
    if (parentNode) {
      item.size = [
        Number.parseFloat(parentNode.style.width),
        Number.parseFloat(parentNode.style.height),
      ]
      this.$set(item.chartOptions, 'size', [
        Number.parseFloat(parentNode.style.width),
        Number.parseFloat(parentNode.style.height),
      ])
      if (
        (item.chartOptions && item.chartOptions.dataId) ||
        this.gridItemIsVistabs(item)
      ) {
        this.dataStore.updateWidget({
          gridItem: item,
          index,
          syncUpdate: true,
        })
      }
    }

    // 如果当前调整大小的是vistabs，则循环更新内部所有组件的大小
    if (this.gridItemIsVistabs(item)) {
      const { activeTabId } = item.chartOptions
      this.gridList.forEach((gridItem, itemIndex) => {
        if (gridItem.chartOptions.parentId === activeTabId) {
          this.updateChartSize(gridItem, itemIndex)
        }
      })
    }
  }

  // 调整当前层级布局中的元素大小
  resizedAllItems() {
    this.layout.forEach((gridItem: GridItemDataInterface) => {
      const index = this.gridList.findIndex((item) => item.i === gridItem.i)
      this.updateChartSize(gridItem, index)
    })
  }

  resized(itemId: string) {
    if (this.isNotAllowed) {
      return
    }

    /** 销毁拖拽状态 */
    this.dataStore.setDraggingItem(null)

    this.$nextTick(() => {
      // 栅格大小调整后再调整组件自身大小
      const index = this.gridList.findIndex((gridItem) => gridItem.i === itemId)
      const item = this.gridList[index]
      this.updateChartSize(item, index)
    })
  }
  resizemove(item: any) {
    this.dataStore.eventBus.$emit('widget-resize-move', item)
  }
  cornermouseup() {
    this.dataStore.setDraggingItem(null)
  }

  // ========> Check if relative component
  gridItemIsVistabs(gridItem: GridItemDataInterface) {
    return gridItem.chartType === vistabsTypeName
  }

  gridItemIsGeoMap(gridItem: GridItemDataInterface) {
    /** 判断gridItem是否为地图组件 */
    return (gridItem.chartType as Array<string>).includes(GEOMAP_NAME)
  }

  gritItemIsFilterForm(gridItem: GridItemDataInterface) {
    /** 检测是否是筛选器控件 */
    return (
      gridItem.chartType === filterFormTypeName ||
      gridItem.chartType[0] === filterFormTypeName
    )
  }

  // ========>  For Text-Editor

  public removeLoading(item: GridItemDataInterface) {
    this.$set(item, 'isLoading', false)
  }

  public highlightFormula(id: number) {
    this.dataStore.setHighlightFormula(id)
  }

  public addErrorBorder(flag: string | number) {
    const element = document.querySelector(`.grid-item-${flag}`)
    element?.classList.add('error-border')
  }

  public removeErrorBorder(flag: string | number) {
    const element = document.querySelector(`.grid-item-${flag}`)
    element?.classList.remove('error-border')
  }

  public handleTextChange(item: GridItemDataInterface, value: string) {
    this.$set(item.chartOptions, 'value', value)
  }

  public handleToolbarAtBorder(gridItem: any, type: 'blur' | 'focus') {
    const toolbar: any = document.querySelector(
      `.box-item-${gridItem.i} .tox-tinymce.tox-tinymce-inline`
    )
    const target: any = document.querySelector(`#grid-item-${gridItem.i}`)
    const { length } = target.style.transform
    const [x, y] = target.style.transform
      .slice(12, length - 1)
      .split(', ')
      .map((loc: any) => Number.parseInt(loc, 10))
    // 505px为当前工具栏的最小宽度，文本框宽度大于该最小值时，工具栏宽度与文本框宽度一致
    const FIX_WIDTH =
      Number.parseInt(target.style.width, 10) > 505
        ? target.style.width
        : '505px'
    // 80px为当前两行工具栏的整体高度，一行为40px
    const FIX_HEIGHT = 80
    // 文本框到画布顶端距离足以容纳工具栏的高度时，将工具栏置于文本框的外部顶端
    if (y >= FIX_HEIGHT && type === 'focus' && toolbar !== null) {
      toolbar.style.transform = target.style.transform
      toolbar.style.width = FIX_WIDTH
      toolbar.style.height = `${FIX_HEIGHT}px`
      toolbar.style.top = `-${FIX_HEIGHT}px`
      toolbar.style.opacity = 1
      toolbar.style.zIndex = 400
    }
    // 否则，工具栏置于文本框的外部底端。文本框内部顶端不考虑，会因为文本框resize而导致title、光标有可能被遮盖。
    if (y < FIX_HEIGHT && toolbar !== null) {
      switch (type) {
        case 'focus': {
          toolbar.style.transform = target.style.transform
          toolbar.style.width = FIX_WIDTH
          toolbar.style.height = `${FIX_HEIGHT}px`
          toolbar.style.top = `${target.clientHeight}px`
          toolbar.style.opacity = 1
          if (
            toolbar.style.display !== 'none' ||
            toolbar.style.visibility !== 'hidden'
          ) {
            // zIndex放在所有样式最后设置，且该400比gridItem的zIndex大，确保多个chart与文本框靠近时工具栏不被遮盖
            toolbar.style.zIndex = 400
          }
          break
        }
        case 'blur': {
          if (toolbar !== null) {
            toolbar.style.zIndex = -1
          }
          break
        }
        default:
          break
      }
    }

    if (type === 'focus' && toolbar !== null) {
      // 获取系统构建画布宽度
      const canvas = document.querySelector('.real-size-block') as HTMLElement
      const canvasWidth = canvas.clientWidth
      const toolBarWidth = Number.parseInt(toolbar.style.width, 10)
      const gridItemWidth = target.clientWidth
      // 当文本框左侧到画布右侧的距离小于当前工具栏的宽度时，让工具栏右侧与文本框右侧对齐，以免被画布遮盖
      if (gridItemWidth < toolBarWidth && toolBarWidth > canvasWidth - x) {
        toolbar.style.left = `-${toolBarWidth - gridItemWidth}px`
      } else {
        toolbar.style.left = '0px'
      }
    }
  }

  public async handleTextBlur(gridItem: any) {
    //  set draggable for the item
    if (this.isPreview || this.isPublish) {
      return
    }

    this.handleToolbarAtBorder(gridItem, 'blur')

    this.$set(gridItem, 'draggable', true)
    this.$nextTick(() => {
      this.dataStore.setCurrentGridItem(gridItem)
    })
    //  Save text value by update Widget
    this.saveTextValue(gridItem)
  }

  private async saveTextValue(gridItem: any) {
    //  Save text value by update Widget
    gridItem.chartOptions.value = this.transferTextValue(
      gridItem.chartOptions.value
    )
    const parameters = buildSaveParameters(gridItem)
    if (this.dashBoardId) {
      parameters.data.dashboardId = this.dashBoardId
    }
    if (gridItem.widgetId) {
      await updateWidget(parameters) // 保存text widget
    }
  }

  // 若value 中存在公式，则替换为公式的值
  public transferTextValue(value: string) {
    const htmlTemplate = value

    const domparser = new DOMParser()
    const parsedDom = domparser.parseFromString(htmlTemplate, 'text/html') // [object HTMLDocument]

    const elements = parsedDom.querySelectorAll('.formula-style')
    if (elements.length > 0) {
      //  replace text with data-result value with innerText
      //  remove .formula-style, add result-style
      elements.forEach((item: any) => {
        item.textContent = item.getAttribute('data-result')
        item.classList.remove('formula-style')
        item.classList.add('result-style')
      })
    }

    const html = parsedDom.documentElement.outerHTML
    const pre = html.indexOf('<body>') + '<body>'.length
    const after = html.indexOf('</body>')

    return html.slice(pre, after)
  }

  getTextData(gridItem: GridItemDataInterface) {
    if (gridItem && gridItem.chartOptions && gridItem.chartType === 'text') {
      const { publishNo } = this
      const dataSend: KeyValueStore = {
        data: {
          id: gridItem.widgetId,
          type: gridItem.chartType, //  'text'
          publishNo,
          text_data: this.isPublish ? 'publish_data' : 'dashboard_data',
        },
      }
      this.$set(gridItem, 'isLoading', true)
      getWidgetData(dataSend)
        .then((response: any) => {
          if (response.data.code === 100) {
            this.$set(
              gridItem,
              'chartOptions',
              response.data.result.extraData.chartOptions
            )
          }
          this.$set(gridItem, 'isLoading', false)
        })
        .catch(() => {
          this.$set(gridItem, 'isLoading', false)
        })
    } else {
      console.error('call error: getTableData must be used by table')
    }
  }

  // ========> For TablePage
  onTablePageChange(
    gridItem: GridItemDataInterface,
    pageConfig: { pageNumber: number; pageSize: number }
  ) {
    this.$set(gridItem.chartOptions, 'curPage', pageConfig.pageNumber)
    this.$set(gridItem.chartOptions, 'pageSize', pageConfig.pageSize)

    let withFilter = false // 判断是否有正在筛选的条件
    if (
      Array.isArray(gridItem.filterFormJson) &&
      gridItem.filterFormJson.length > 0 &&
      gridItem.filterFormJson.some(
        (data) => this.executeFilterFormData[data.producerId]
      )
    ) {
      withFilter = true
    }
    this.getTableData(gridItem, withFilter, 'pageChange')
  }

  getTableData(
    gridItem: GridItemDataInterface,
    withFilterSend?: boolean,
    source?: 'pageChange'
  ) {
    if (gridItem && gridItem.chartOptions && gridItem.chartType === 'table') {
      const { publishNo } = this
      /** 表格组件有点特殊，自带分页组件，所以在关联状态时要做二次确认 */
      const withFilter =
        this.currentFilterFormAsCharts.includes(gridItem.widgetId) ||
        withFilterSend
      const filterFormJson =
        withFilter && gridItem.filterFormJson ? gridItem.filterFormJson : []
      const dataSend: KeyValueStore = {
        data: {
          id: gridItem.chartOptions.dataId,
          type: gridItem.chartOptions.dataType,
          tableJson: {
            curPage:
              source !== 'pageChange' && withFilter
                ? 1
                : gridItem.chartOptions.curPage,
            name: '_record_id_',
            pageSize: gridItem.chartOptions.pageSize,
            sortCol: gridItem.chartOptions.sortCol, //  sortCol; interact info in -> filters
            filters: withFilter
              ? mergeFilterFormFilters(
                  gridItem.chartOptions.filter || [],
                  filterFormJson
                )
              : gridItem.chartOptions.filter,
          },
          publishNo,
        },
      }
      this.$set(gridItem, 'isLoading', true)

      getWidgetData(dataSend)
        .then((response: any) => {
          if (response.data.code === 418) {
            this.$message.error(response.data.tips)
          }
          if (response.data.code === 1106) {
            this.errorFilterWidgets.push(gridItem.i)
            this.errorMsgMap[gridItem.i] = response.data.message
          }
          if ([100, 1106].includes(response.data.code)) {
            const gridItemClone = cloneDeep(gridItem)
            const { chartOptions } = gridItemClone
            const index = this.getGridIndex(gridItem.i as string, 'i')
            const gridItemSet = {
              ...gridItemClone,
              isLoading: false,
              chartOptions: {
                ...chartOptions,
                curPage: response.data.result?.page?.curPage || 0,
                totalElements: response.data.result?.page?.totalElements || 0,
                pageSize: response.data.result?.page?.pageSize || 50,
                value: response.data.result
                  ? parseGetDataResult(
                      response.data.result,
                      undefined,
                      gridItem
                    )
                  : [],
              },
            }
            this.dataStore.updateWidget({
              gridItem: gridItemSet,
              index,
              onlyLocal: true,
            })
          } else {
            this.$set(gridItem, 'isLoading', false)
          }
          if (withFilter) {
            setTimeout(() => {
              this.releaseLoadingTag(gridItem.widgetId)
            }, 100)
          }
        })
        .catch(() => {
          this.$set(gridItem, 'isLoading', false)
          if (withFilter) {
            setTimeout(() => {
              this.releaseLoadingTag(gridItem.widgetId)
            }, 100)
          }
        })
    } else {
      this.releaseLoadingTag(gridItem.widgetId)
      console.error('call error: getTableData must be used by table')
    }
  }
  /**
   * 排序
   */
  private onTableSortchange(
    gridItem: GridItemDataInterface,
    index: number,
    sortData: { key: string; sort: 'desc' | 'asc' }
  ) {
    if (sortData.key) {
      this.$set(gridItem.chartOptions, 'sortCol', [
        { name: sortData.key, sort: sortData.sort },
      ])
      this.$set(gridItem.chartOptions, 'curPage', 1)
      let withFilter = false // 判断是否有正在筛选的条件
      if (
        Array.isArray(gridItem.filterFormJson) &&
        gridItem.filterFormJson.length > 0 &&
        gridItem.filterFormJson.some(
          (data) => this.executeFilterFormData[data.producerId]
        )
      ) {
        withFilter = true
      }
      this.getTableData(gridItem, withFilter)
    }
  }
  /**
   * 更改表格列宽度，需要记录下来
   */
  private onTableColumnWidthChange(
    gridItem: GridItemDataInterface,
    columnWidthData: object
  ) {
    this.$set(gridItem.chartOptions, 'columnWidthData', columnWidthData)
  }

  // ========> For Chart
  onChartInstanceInit(chartInstance: any, item: GridItemDataInterface) {
    this.dataStore.setChartInstanceMaps({
      instance: chartInstance,
      key: item.i,
    })
  }

  /**
   * 保存graph node 节点位置
   */
  private saveGraphNodePosition(
    position: { id: string; x: number; y: number }[],
    gridItem: GridItemDataInterface,
    index: number
  ) {
    this.$set(gridItem.chartOptions, 'nodePosition', position)
    gridItem.chartOptions.value.nodes.forEach((node: any) => {
      const targetPosition = position.filter((p) => p.id === node.id)[0]
      if (targetPosition) {
        node.x = targetPosition.x
        node.y = targetPosition.y
      }
    })
    this.dataStore.updateWidget({ gridItem, index })
  }

  private onDiffBarDrag(
    value: any,
    gridItem: GridItemDataInterface,
    index: number
  ) {
    const { fullData } = value
    const { valueKey } = gridItem.chartOptions
    const diffValue = fullData.filter(
      (field: any) => field.diffField === '差值'
    )[0][valueKey]
    const baseValue = fullData.filter(
      (field: any) => field.diffField !== '差值'
    )[0][valueKey]
    const ratio = +((diffValue * 100) / baseValue).toFixed(3)
    this.reverseSimulationValue = ratio
    console.log('onDiffBarDrag', value, ratio, index)
  }

  /**
   * chart触发的事件
   */
  private onChartEvent(data: any, item: any) {
    const index = this.gridList.findIndex((gridItem) => gridItem.i === item.i)

    switch (data.name) {
      case 'lineGraphNodeDragend': // graph-linechart 拖动节点位置，需要记住位置
        if (this.isEdit) {
          this.saveGraphNodePosition(data.value, item, index)
        }
        break
      case 'selectedChange':
      case 'graphNodeClick': // graph node 点击
        if (this.isPreview || this.isPublish) {
          this.dataStore.eventBus.$emit('graph-node-click', data, item)
        }
        break
      case 'dragBar': // diffBar 拖动bar（为反向模拟推演）
        this.onDiffBarDrag(data.value, item, index)
        break
      default:
        break
    }
  }

  // ========> For Operator Configuration

  /**
   * 判断是否符合反向模拟推演特征
   */
  private isReverseSimulation(gridItem: GridItemDataInterface) {
    const { diffBar, dataId } = gridItem.chartOptions
    if (this.allWidgetList.length === 0) {
      // pipeline widgetList
      return false
    }
    const target = this.allWidgetList.filter(
      (widget: any) => widget.tid === dataId
    )[0]
    if (diffBar && target && target.taskName.includes('反向模拟推演')) {
      return true
    }
    return false
  }

  /**
   * 反向模拟推演执行
   */
  private async saveReverseSimulation(gridItem: GridItemDataInterface) {
    if (this.isVisitor || this.isLock) {
      return
    }
    const { dataId } = gridItem.chartOptions
    const target = this.allWidgetList.filter(
      (widget: any) => widget.tid === dataId
    )[0]
    const { publishNo } = this
    const response: any = await simpleUpdate({
      data: {
        ...target.widgets[0].data.info,
        data: {
          gdp: this.reverseSimulationValue,
        },
        id: dataId,
        // pipelineId,
        projectId: this.projectId,
        publishNo,
      },
    })
    const parameters = buildSaveParameters(gridItem)
    if (this.dashBoardId) {
      parameters.data.dashboardId = this.dashBoardId
    }

    if (response.data.code === 100) {
      this.executeConfiguration(gridItem) // 执行
    } else {
      const { message, tips } = response.data
      this.$message.error(message || tips)
    }
  }

  /**
   * 保存控件参数
   */
  private async saveConfigurationParams(gridItem: GridItemDataInterface) {
    if (this.configurationExecuteId || this.isVisitor || this.isLock) {
      return
    }
    if (this.isReverseSimulation(gridItem)) {
      this.saveReverseSimulation(gridItem)
      return
    }
    const { dataId, value = {} } = gridItem.chartOptions
    const { publishNo } = this
    const response: any = await simpleUpdate({
      data: {
        ...gridItem.chartOptions.info,
        data: value,
        id: dataId,
        projectId: this.projectId,
        publishNo,
      },
    })
    const parameters = buildSaveParameters(gridItem)
    if (this.dashBoardId) {
      parameters.data.dashboardId = this.dashBoardId
    }
    if (gridItem.widgetId && !this.isPublish) {
      await updateWidget(parameters) // 保存configuration widget
    }

    if (response.data.code === 100) {
      this.executeConfiguration(gridItem) // 执行
    } else {
      const { message, tips } = response.data
      this.$message.error(message || tips)
    }
  }

  /**
   * 停止执行
   */
  private async stopConfigurationExecute() {
    this.dataStore.setConfigurationExecuteId('')
    const { publishNo } = this
    const response = await pipelineStop({
      data: {
        projectId: this.projectId,
        sessionId: this.configurationExecuteSessionId,
        publishNo,
      },
    })
    if (response.data.code === 100) {
      this.$message.success('停止成功')
      VisualizationStore.setConfigurationExecuteId('')
    } else {
      this.$message.error(response.data.message || '停止失败，请稍候再试')
    }
  }

  /**
   * 执行控件
   */
  private async executeConfiguration(gridItem: GridItemDataInterface) {
    const { pipelineId, taskId, dataId } = gridItem.chartOptions
    this.dataStore.setConfigurationExecuteId(gridItem.widgetId)
    const { publishNo }: any = this
    const response: any = await pipelineExecute({
      data: {
        id: pipelineId,
        taskId: taskId || dataId,
        projectId: this.projectId,
        sampling: false,
        publishNo,
      },
    })
    if (response.data.code === 100) {
      const [sessionId] = response.data.result
      this.configurationExecuteSessionId = sessionId
      this.dataStore.setConfigurationSessionId(sessionId)
      this.$socket.emit('queryStatus', { pipelineId, sessionId })
    }
  }

  // ========> For Filter Form

  showFilterForm() {
    return !this.pipelineId
      ? true
      : this.dataset && this.dataset.length > 0 && this.pipelineId
  }

  /*  检查并清除filter-form的关联chart */
  clearFilterFormAssociationChart(widgetId: string | number) {
    this.gridList.forEach((gridItem: GridItemDataInterface, index: number) => {
      if (
        checkIsFilterForm(gridItem) &&
        ((gridItem.chartOptions?.associationChart || []) as Array<
          string | number
        >).includes(widgetId)
      ) {
        const { chartOptions } = gridItem
        this.$set(
          chartOptions,
          'associationChart',
          chartOptions.associationChart.filter(
            (id: string | number) => id !== widgetId
          )
        )
        this.dataStore.updateWidget({
          gridItem: {
            ...gridItem,
            chartOptions,
          },
          index,
        })
      }
    })
  }

  /** 鼠标移入filter-form */
  handleFilterFormMouseEnter(gridItem: GridItemDataInterface) {
    this.hoverFilterFormInfo = {
      i: gridItem.i as any,
      widgetId: gridItem.widgetId,
    }
  }

  /** 鼠标移出fitler-form */
  handleFilterFormMouseLeave() {
    this.hoverFilterFormInfo = null
  }

  /** 筛选控件中的筛选条件改变 */
  filterChange(data: any, gridItem: GridItemDataInterface) {
    if (this.isPreview || this.isPublish) {
      return
    }
    const index = this.getGridIndex(gridItem.i, 'i')
    const chartOptions = { ...gridItem.chartOptions, formData: {} }
    this.dataStore.updateWidget({
      gridItem: {
        ...gridItem,
        chartOptions,
      },
      index,
    })
  }

  /** 判断筛选执行情况 */
  checkIsRunning(gridItem: GridItemDataInterface) {
    return (
      this.loadingMaps[`widget_${gridItem.widgetId}`] &&
      this.loadingMaps[`widget_${gridItem.widgetId}`].length > 0
    )
  }

  isFilterForm(item: GridItemDataInterface) {
    return checkIsFilterForm(item)
  }

  filterFormClicked(item: GridItemDataInterface) {
    this.dataStore.setCurrentFilterFormAsCharts(
      item.chartOptions?.associationChart || []
    )
  }

  startfilter(item: GridItemDataInterface) {
    this.filterFormExecute(true, item)
  }

  stopfilter(item: GridItemDataInterface) {
    this.filterFormExecute(false, item)
  }

  /** 执行或者停止筛选 */
  filterFormExecute(isStart: boolean, gridItem: GridItemDataInterface) {
    this.filterFormClicked(gridItem)

    this.dataStore.setCurrentGridItem(gridItem)
    this.$nextTick(() => {
      if (isStart) {
        this.loadAssociationCharts(gridItem)
      } else {
        this.dataStore.setFilterFormExecuteId('')
        this.$set(this.loadingMaps, `widget_${gridItem.widgetId}`, [])
        delete this.executeFilterFormData[gridItem.widgetId]
      }
    })
  }

  /** 重置 */
  resetfilter(gridItem: GridItemDataInterface) {
    this.filterFormClicked(gridItem)
    const index = this.getGridIndex(gridItem.i, 'i')
    let queryData = {} as any
    const FilterFormReference = this.$refs[`filter-form-${gridItem.i}`] || []
    const that = this

    if (FilterFormReference && (FilterFormReference as any)[0]) {
      queryData = (FilterFormReference as any)[0].getResetData() || {}
    }
    this.dataStore.updateWidget({
      gridItem: {
        ...gridItem,
        chartOptions: {
          ...gridItem.chartOptions,
          queryData,
        },
      },
      index,
      onSuccess(gridItemSaved: gridItemFilterInterface) {
        that.loadAssociationCharts(gridItemSaved)
        delete that.executeFilterFormData[gridItem.widgetId]
      },
    })
  }

  /** 加载关联图表chart */
  loadAssociationCharts(gridItem: GridItemDataInterface) {
    const associationCharts = ((gridItem.chartOptions?.associationChart ||
      []) as Array<string | number>).filter((id) => {
      return this.gridList.findIndex((grid) => grid.widgetId === id) > -1
    })
    if (associationCharts.length === 0) {
      this.$message.info('筛选器没有有效关联组件')
      this.dataStore.setFilterFormExecuteId('')
      return
    }
    this.dataStore.setIsSettingPanelChangeLock(true)
    this.dataStore.setFilterFormExecuteId(gridItem.widgetId)
    this.$set(
      this.loadingMaps,
      `widget_${gridItem.widgetId}`,
      cloneDeep(associationCharts)
    )
    const filterFormWidgetJSON = getFilterDataFromFilterForm(gridItem)
    this.executeFilterFormData[gridItem.widgetId] = filterFormWidgetJSON
    associationCharts.forEach((widgetId) => {
      const asGridItem = this.gridList.find((item) => {
        return item.widgetId === widgetId
      })
      if (asGridItem) {
        this.$set(asGridItem, 'filterFormJson', filterFormWidgetJSON)
        this.getWidgetData(
          asGridItem.chartOptions,
          asGridItem,
          this.getGridIndex(asGridItem.widgetId, 'widgetId'),
          false,
          true
        )
      }
    })
  }

  /**
   * 获取筛选参数
   */
  getFilterFormData(dataId: string, componentId: string): KeyValueStore | null {
    const gridFilters = this.gridList.filter((gridItem) => {
      return gridItem.chartType === 'filter' && gridItem.dataId === dataId
    })
    if (gridFilters.length > 0) {
      const result: KeyValueStore = {}
      gridFilters.forEach((item) => {
        item.fieldConfig.forEach((fieldInfo: ControlConfigurationInterface) => {
          if (fieldInfo.associationChart.includes(componentId)) {
            result[fieldInfo.column.value] = {
              compType: fieldInfo.controlRender,
              columnType: fieldInfo.columnType,
              value: fieldInfo.value,
            }
          }
        })
      })
      return result
    }
    return null
  }

  getFilterItem(
    fieldName: string,
    value: any,
    controlType: string,
    columnType?: string
  ): KeyValueStore | null {
    let result: any = null
    switch (controlType) {
      case componentTypeEnum.radio:
      case componentTypeEnum.select:
        if (isString(value) && value !== '') {
          result = {
            col: fieldName,
            filterType: '=',
            values: [],
            checkedList: [value],
          }
        }
        break

      case componentTypeEnum.checkbox:
        if (value && isArray(value) && value.length > 0) {
          result = {
            col: fieldName,
            type: columnType,
            filterType: 'in',
            values: [],
            checkedList: value,
          }
        }
        break

      case componentTypeEnum.rangeDay:
        if (
          value &&
          isArray(value) &&
          value.length === 2 &&
          value[0] &&
          value[1]
        ) {
          result = {
            col: fieldName,
            filterType: '[a,b]',
            type: 'timestamp', // timestamp | datetime
            values: [value[0], value[1]],
            format: 'yyyy-MM-dd',
            checkedList: [value[0], value[1]],
          }
        }
        break
      case componentTypeEnum.rangeMonth:
        if (
          value &&
          isArray(value) &&
          value.length === 2 &&
          value[0] &&
          value[1]
        ) {
          result = {
            col: fieldName,
            filterType: '[a,b]',
            type: 'timestamp', // timestamp | datetime
            values: [value[0], value[1]],
            format: 'yyyy-MM',
            checkedList: [value[0], value[1]],
          }
        }

        break
      case componentTypeEnum.rangeYear:
        if (
          value &&
          isArray(value) &&
          value.length === 2 &&
          value[0] &&
          value[1]
        ) {
          result = {
            col: fieldName,
            filterType: '[a,b]',
            type: 'timestamp', // timestamp | datetime
            values: [value[0], value[1]],
            format: 'yyyy',
            checkedList: [value[0], value[1]],
          }
        }
        break
      default:
        break
    }

    return result
  }

  /**
   * 根据 gridItem 获取graph widget 数据
   */
  private async getGraphData(
    gridItem: GridItemDataInterface,
    withFilter?: boolean
  ) {
    this.$set(gridItem, 'isLoading', true)
    const callback = withFilter
      ? (data: any) => filterGraphData(gridItem.filterFormJson || [], data)
      : undefined

    const { publishNo } = this
    const value = await getWidgetGraphData(
      gridItem,
      this.projectId,
      callback,
      publishNo
    )
    /** 将数据缓存到graphService中，以便筛选组件使用 */
    // visGraphService.save({id: gridItem.chartOptions?.dataId || '', data: value})
    const { nodePosition = [] } = gridItem.chartOptions
    value.nodes.forEach((node: any) => {
      const targetPosition = (nodePosition as Array<{
        id: string
        x: number
        y: number
      }>).filter((p) => p.id === node.id)[0]
      if (targetPosition) {
        node.x = targetPosition.x
        node.y = targetPosition.y
      }
    })
    this.$set(gridItem.chartOptions, 'value', value)
    // this.$set(gridItem, 'chartType', chartTypeOld)
    this.$set(gridItem, 'showChart', true)
    this.$set(gridItem, 'isLoading', false)
    setTimeout(() => {
      this.releaseLoadingTag(gridItem.widgetId)
    }, 100)
  }

  /**
   * 获取widget数据
   */
  getWidgetData(
    dataSetting: KeyValueStore,
    gridItem: GridItemDataInterface,
    index: number,
    isAutoSave?: boolean,
    withFilter?: boolean,
    keepCurrent?: boolean
  ) {
    const filters: KeyValueStore | null = this.getFilterFormData(
      dataSetting.dataId,
      gridItem.i as string
    )
    const filterSend = [] as Array<any>
    if (filters) {
      Object.keys(filters).forEach((fieldName: string) => {
        const controlRender = filters[fieldName].compType
        const value = filters[fieldName].value as any
        if (
          (value &&
            !['rangeDay', 'rangeYear', 'rengeMonth'].includes(controlRender) &&
            value[0] !== '') ||
          (value &&
            ['rangeDay', 'rangeYear', 'rengeMonth'].includes(controlRender) &&
            value[0] &&
            value[1])
        ) {
          const filterItem = this.getFilterItem(
            fieldName,
            value,
            controlRender,
            filters[fieldName].columnType
          )
          if (filterItem) {
            filterSend.push(filterItem)
          }
        }
      })
    }
    const filterFormJson = gridItem.filterFormJson || []

    if (gridItem.chartType === 'table') {
      this.getTableData(gridItem, withFilter)
      return
    }
    if (gridItem.chartType === 'text') {
      this.getTextData(gridItem)
      return
    }
    if (
      GRAPH_ARRAY.includes(
        Array.isArray(gridItem.chartType)
          ? gridItem.chartType[1]
          : gridItem.chartType
      )
    ) {
      this.getGraphData(gridItem, withFilter)
      return
    }

    const query = {
      data: gridItem.dataQuery?.data,
    }
    ;(<any>query.data.widgetJson.config.filters) = filterSend
    /*
    gridItem.chartOptions = {
      ...gridItem.chartOptions,
      ...{
        labelIsShow: false,
      },
    }
     */
    if (enableConvertToCoor(gridItem.chartOptions)) {
      // 需要映射地理数据（省份、城市、ip等映射成经纬度）
      query.data.convert = gridItem.chartOptions.geoTransform.fields
    }

    /** 筛选数据 */
    if (withFilter && query.data?.widgetJson?.config) {
      query.data.widgetJson.config.filters = mergeFilterFormFilters(
        query.data.widgetJson.config.filters || [],
        filterFormJson
      )
    }

    query.data.publishNo = this.publishNo

    this.$set(gridItem, 'isLoading', true)
    this.errorFilterWidgets = this.errorFilterWidgets.filter(
      (item) => item !== gridItem.i
    )
    getWidgetData(query)
      .then((response) => {
        if (response.data.code === 1106) {
          this.errorFilterWidgets.push(gridItem.i)
          this.errorMsgMap[gridItem.i] = response.data.message
        }
        /** 在筛选器模式下有可能筛选不出数据，状态为1106 */
        if ([100, 1106].includes(response.data.code)) {
          setTimeout(() => {
            this.$set(this.gridList[index], 'isLoading', false)
          }, 100)

          const chartType = Array.isArray(gridItem.chartType)
            ? gridItem.chartType[1]
            : gridItem.chartType
          if (isAutoSave) {
            const parameters = buildSaveParameters(gridItem)
            // parameters.data.tid = gridItem.dataId
            // parameters.data.type = 'task'
            if (!parameters.projectId) {
              parameters.projectId = this.projectId
            }
            if (this.dashBoardId) {
              parameters.data.dashboardId = this.dashBoardId
            }
            saveWidget(parameters).then((responsesave) => {
              if (responsesave.data.code === 100) {
                this.$set(gridItem, 'widgetId', responsesave.data.result)
                // this.$set(gridItem.formData, 'dataId',  gridItem.dataId)
                // 处理数据
                const dataResult =
                  parseGetDataResult(
                    response.data?.result || {
                      columns: [],
                      data: [],
                      extraData: {},
                    },
                    chartType,
                    gridItem
                  ) || []
                // if (query.data.widgetJson !== undefined) {
                //   dataResult = restructGetDataResult(
                //     dataResult,
                //     query.data.widgetJson
                //   )
                // }

                this.dataStore.gridItemSyncBindData({
                  data: {
                    query,
                    data: dataResult,
                    columns: gridItem.columns,
                    formData: gridItem.chartOptions,
                  },
                  gridItem,
                  index,
                  withFilter,
                  noChangeCurrent: !!keepCurrent,
                })
                console.error('getWidgeData save done')
                this.onSave()
              }
            })
          } else {
            const dataResult = parseGetDataResult(
              response.data?.result || { columns: [], data: [], extraData: {} },
              chartType,
              gridItem
            )
            // if (query.data.widgetJson !== undefined) {
            //   dataResult = restructGetDataResult(
            //     dataResult,
            //     query.data.widgetJson
            //   )
            // }

            this.dataStore.gridItemSyncBindData({
              data: {
                query,
                data: dataResult,
                columns: gridItem.columns,
                formData: gridItem.chartOptions,
              },
              gridItem,
              index,
              withFilter,
              noChangeCurrent: !!keepCurrent,
            })
          }
        } else {
          this.$set(gridItem, 'isLoading', false)
        }
        if (withFilter) {
          setTimeout(() => {
            this.releaseLoadingTag(gridItem.widgetId)
          }, 100)
        }
      })
      .catch((error) => {
        console.log('error', error)
        this.$set(gridItem, 'isLoading', false)
        if (withFilter) {
          setTimeout(() => {
            this.releaseLoadingTag(gridItem.widgetId)
          }, 100)
        }
      })
  }
}
</script>

<style lang="less" scoped>
@import '@/styles/scroll-bar';

:root {
  --title-color: #373b52;
  --component-bg-color: #fff;
  --component-bg-color-hover: #fff;
  --component-bg-color-tspt: #fff;
  --component-bg-color-oneop: #fff;
  --component-border-color: #e9e9e9;
  --component-border-color-hover: #c5c9ff;
  --component-border-color-active: #6771ef;
  --component-shadow: none;
  --component-shadow-active: none;
  --controller-label: #373b52;
  --controller-input-border: #e9e9e9;
  --controller-innput-arrow: #5d637e;
  --controller-slider-rail: rgba(0, 0, 0, 0.08);
  --controller-palceholder: rgba(0, 0, 0, 0.25);
  --table-th: #fafafa;
  --table-tr: #fafafa;
  --table-pag-active-num: #6973ff;
  --table-pag-num: #5d637e;
  --table-row-hover: rgb(105, 115, 255);
  --table-border: rgba(217, 217, 217, 0.4);
  --content-box-bg: rgba(235, 234, 239, 1);
  --tooltip-bg: rgb(255, 255, 255);
}

.widgets-layout {
  height: calc(100% - 42px);
  position: relative;
  width: 100%;

  .drag-marker {
    background: var(--component-bg-color-tspt);
    color: var(--title-color);
    margin: 4px 8px;
    position: absolute;
    right: 0;
    top: 0;
    z-index: 1009;

    &:hover,
    &.is-loading {
      background: var(--component-bg-color-tspt);
    }
  }

  .progress-bar {
    animation: gradient 1.5s ease infinite;
    background: linear-gradient(
      90deg,
      #c0bfff78 25%,
      #5760e6 37%,
      #c0bfff78 63%
    );
    background-size: 400% 100%;
    height: 3px;
    position: absolute;
    top: 0;
    width: 100%;
    z-index: 999;
  }

  .text-editor {
    color: var(--title-color);
    display: flex;
    flex-direction: column-reverse;
    height: 100%;
    padding: 0 15px;
    position: relative;

    // z-index: 2010;
    /deep/ .editor-box {
      margin-top: 6px;
    }

    /deep/ .editor-box p:only-child {
      margin-bottom: 0;
    }
  }

  .people-heat {
    height: calc(100% - 20px);
    padding: 0 15px;
  }

  .pre-pub-mode {
    /deep/ .editor-box {
      margin-top: 6px;
    }
  }

  .interact-mask {
    background: var(--component-bg-color-oneop) !important;
    height: calc(100% - 38px);
    margin-top: 38px;
    position: absolute;
    width: 100%;
    z-index: 100;

    &.no-title {
      height: 100%;
      margin-top: 0;
    }
  }

  .loading-mask,
  .no-data {
    align-content: center;
    align-items: center;
    background: transparent;
    display: flex;
    height: 100%;
    justify-content: center;
    text-align: center;
    transform: translateY(-40px);
    width: 100%;
    z-index: 100;

    .loading-status-img,
    .no-data-img {
      display: block;
      height: auto;
      max-width: 75%;
      width: 208px;
    }

    .no-data-block {
      align-items: center;
      display: flex;
      flex-flow: column;
      justify-content: center;
    }

    .no-data-text {
      color: #d9d9d9;
    }
  }

  .id-box {
    background: #5760e6;
    color: #fff;
    display: inline-block;
    font-size: 12px;
    height: 16px;
    left: 0;
    line-height: 16px;
    max-width: calc(100% - 16px);
    padding: 0 4px;
    position: absolute;
    top: 0;
    z-index: 999;
  }

  .grid-item {
    background-color: var(--component-bg-color);
    border: 1px solid var(--component-border-color);
    border-radius: 4px !important;
    box-shadow: var(--component-shadow);
    box-sizing: border-box;
    overflow: hidden;
    position: relative;

    &.active,
    &:hover {
      border: 1px solid var(--component-border-color-hover);
    }

    .grid-item-close,
    .grid-item-copy {
      color: var(--title-color);
    }

    .grid-item-close,
    .grid-item-edit,
    .grid-item-execute,
    .grid-item-copy {
      cursor: pointer;
      display: inline-block;
      font-size: 16px;
      line-height: 32px;
      margin-right: 8px;
      opacity: 0;
      pointer-events: none;

      img {
        height: auto;
        width: 24px;
      }
    }

    .grid-item-drag-handler {
      height: 10px;
      left: 0;
      min-width: 30px;
      position: absolute;
      right: 55px;
      top: 0;
      z-index: 1009;
    }

    &:hover {
      .grid-item-close,
      .grid-item-edit,
      .grid-item-execute,
      .grid-item-copy,
      /deep/ .comp-filter-form-toolbar {
        opacity: 1;
        pointer-events: all;

        &.disable {
          cursor: not-allowed;
          opacity: 0.5 !important;
        }
      }
    }

    .loading-status {
      align-content: center;
      align-items: center;
      bottom: 0;
      color: var(--title-color);
      display: flex;
      flex-flow: row nowrap;
      font-size: 12px;
      font-weight: 500;
      justify-content: center;
      left: 0;
      letter-spacing: 1px;
      line-height: 36px;
      position: absolute;
      right: 0;
      top: 0;
      vertical-align: middle;
      z-index: 1000;

      .loading-text {
        display: inline-block;
        padding-left: 4px;
      }

      .sync-icon {
        color: #5760e6;
        font-size: 24px;
      }
    }
  }

  .grid-item.report-mode {
    border: 1px solid rgba(233, 233, 233, 1);

    &.grid-item-highlight {
      border: 1px solid #c5c9ff;
    }
  }

  .grid-item.disable-border {
    border: 1px solid transparent !important;

    &:hover {
      border: 1px solid transparent;
    }

    .grid-item-close {
      display: none;
    }
  }

  .grid-item-highlight {
    border: 1px solid #c5c9ff;
  }

  .title-box {
    color: var(--title-color);
    font-size: 14px;
    font-weight: 500;
    font-weight: 600;
    height: 38px;
    letter-spacing: 1px;
    line-height: 38px;
    overflow: hidden;
    padding: 0 16px;
    padding-right: 55px;
    text-overflow: ellipsis;
    white-space: nowrap;
    width: 100%;
  }

  .no-title.title-box {
    display: none;
  }

  /deep/ .vue-grid-item.vue-grid-placeholder {
    background-color: var(--grid-placeholder-background);
    border: 1px dashed #fff;
    opacity: 1;
  }

  /deep/ .vue-resizable-handle {
    z-index: 2000;
  }
}

/deep/ .tox-tinymce.tox-tinymce-inline {
  float: left;
  opacity: 0;
  position: absolute;
  z-index: -1;
}

/deep/ .chart-title {
  font-size: 14px !important;
  font-weight: 600;
  // height: 38px;
  letter-spacing: 1px;
  line-height: 38px;
  padding: 0 16px;
  text-align: left !important;
}

.error-border {
  border: 1px solid #e6505a !important;
}

@keyframes gradient {
  0% {
    background-position: 100% 50%;
  }

  100% {
    background-position: 0% 50%;
  }
}

.filter-form-hander {
  background: transparent;
  box-sizing: border-box;
  height: 100%;
  left: 0;
  padding-top: 30px;
  position: absolute;
  top: 0;
  width: 100%;
  z-index: 8;
}

.noWidgetsTips {
  color: var(--title-color);
  height: 100%;
  left: 50%;
  position: absolute;
  top: calc(50% - 20px);
  transform: translateX(-50%);
}

.up-split {
  .blank {
    height: 54px;
    width: 100%;
  }

  .up-split-box {
    display: flex;
    height: 18px;
    justify-content: space-between;

    .top-left {
      border-bottom: 1px solid rgba(151, 151, 151, 1);
      border-right: 1px solid rgba(151, 151, 151, 1);
      height: 18px;
      width: 18px;
    }

    .top-right {
      border-bottom: 1px solid rgba(151, 151, 151, 1);
      border-left: 1px solid rgba(151, 151, 151, 1);
      height: 18px;
      width: 18px;
    }
  }
}

.down-split {
  .down-split-box {
    display: flex;
    height: 18px;
    justify-content: space-between;

    .bottom-left {
      border-right: 1px solid rgba(151, 151, 151, 1);
      border-top: 1px solid rgba(151, 151, 151, 1);
      height: 18px;
      width: 18px;
    }

    .bottom-right {
      border-left: 1px solid rgba(151, 151, 151, 1);
      border-top: 1px solid rgba(151, 151, 151, 1);
      height: 18px;
      width: 18px;
    }
  }
}

.blank {
  height: 54px;
  width: 100%;
}

.gap {
  background: #f8f8f8;
  height: 20px;
  width: 100%;
}

/deep/ .el-table__body-wrapper.remove-scroll {
  overflow: hidden;
}
</style>
